#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using UTable.Input;
using UTable.Input.MultiTouch;

namespace UTable.Objects.Controls
{
    public class ControlInfo: IVisualNode
    {
        private String _id;
        private double _width;
        private double _height;
        private Collection<ControlInfo> _children;
        private List<ControlInfo> _sortedChildren;
        private ControlInfo _parent;
        private IObjectEntity _entity;
        private IControl _control;
        private Geometry _clip;
        private ControlInfoComparer _comparer;
        private GeneralTransform _transformToContent;
        private GeneralTransform _transformFromContent;
        private bool _isHitTestVisible = true;
        private bool _isHitTestEnabled = true;
        private Rect _bound = new Rect(double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity);


        private class ControlInfoComparer : IComparer<ControlInfo>
        {
            #region IComparer<IObject> Members

            public int Compare(ControlInfo x, ControlInfo y)
            {
                return y.ZIndex - x.ZIndex;
            }

            #endregion
        }

        internal void ChangeLayoutProperty(LayoutPropertyChangedEventArgs args)
        {
            switch (args.PropertyType)
            {
                case LayoutPropertyType.WIDTH:
                    this._width = (double)args.NewValue;
                    break;
                case LayoutPropertyType.HEIGHT:
                    this._height = (double)args.NewValue;
                    break;
                case LayoutPropertyType.Clip:
                    this._clip = (Geometry)args.NewValue;
                    break;
            }
        }

        public ControlInfo()
        {
            _children = new ObservableCollection<ControlInfo>();
            (_children as ObservableCollection<ControlInfo>).CollectionChanged 
                += new NotifyCollectionChangedEventHandler(ControlInfo_CollectionChanged);
            _sortedChildren = new List<ControlInfo>();
            _comparer = new ControlInfoComparer();
        }

        private void ControlInfo_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    OnChildAdded(e.NewItems[0] as ControlInfo, e.NewStartingIndex);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    OnChildRemoved(e.OldItems[0] as ControlInfo);
                    break;
                case NotifyCollectionChangedAction.Replace:
                    OnChildAdded(e.NewItems[0] as ControlInfo, e.NewStartingIndex);
                    OnChildRemoved(e.OldItems[0] as ControlInfo);
                    break;
                case NotifyCollectionChangedAction.Reset:
                    foreach (ControlInfo child in this.Children)
                        OnChildRemoved(child);
                    break;
            }
        }

        internal void ResortChildren()
        {
            _sortedChildren.Clear();
            foreach (ControlInfo info in _children)
                _sortedChildren.Add(info);
            _sortedChildren.Sort(_comparer);
        }

        private void OnChildAdded(ControlInfo child, int startingIndex)
        {
            ResortChildren();
        }
        private void OnChildRemoved(ControlInfo child)
        {
            _sortedChildren.Remove(child);
        }

        private void OnContentChanged(object oldValue, object newValue)
        {
            //if (Control is UContentControl)
            //    (Control as UContentControl).OnUContentChanged(oldValue, newValue);
            //if (Control is UScrollViewer)
            //    (Control as UScrollViewer).OnUContentChanged(oldValue, newValue);
            //if (Control is UContentPresenter)
            //    (Control as UContentPresenter).OnUContentChanged(oldValue, newValue);
        }

        private void IsHitTestVisibleChanged(object oldValue, object newValue)
        {
            lock (this)
            {
                this.IsHitTestEnabled = (bool)newValue;
            }
        }

        private void ZIndexChanged(object oldValue, object newValue)
        {
            lock (this)
            {
                this.ZIndex = (int)newValue;
                if (this.Parent != null)
                    this.Parent.ResortChildren();
            }
        }

        private void OpacityChanged(object oldValue, object newValue)
        {
            lock (this)
            {
                this.Opacity = (double)newValue;
            }
        }

        private void VisibilityChanged(object oldValue, object newValue)
        {
            lock (this)
            {
                this.Visibility = (Visibility)newValue;
            }
        }

        private void BackgroundChanged(object oldValue, object newValue)
        {
            /*
            lock (this)
            {
                this.Background = ((Brush)newValue).CloneCurrentValue();
                this.Background.Freeze();
            }*/

        }
        private void ClipChanged(object oldValue, object newValue)
        {
            this._clip = ((Geometry)newValue).CloneCurrentValue();
            this._clip.Freeze();
        }

        private void OnRenderTransformChanged(object oldValue, object newValue)
        {
            this.Fresh();
        }

        private void WidthChanged(object oldValue, object newValue)
        {
            this._width = (double)newValue;
        }
        private void HeightChanged(object oldValue, object newValue)
        {
            this._height = (double)newValue;
        }
        private void MinWidthChanged(object oldValue, object newValue)
        {
        }
        private void MaxWidthChanged(object oldValue, object newValue)
        {
        }
        private void MinHeightChanged(object oldValue, object newValue)
        {
        }
        private void MaxHeightChanged(object oldValue, object newValue)
        {
        }

        private bool IsInBound(Point p)
        {
            if (!double.IsInfinity(Bound.Left) && p.X < Bound.Left)
                return false;
            if (!double.IsInfinity(Bound.Right) && p.X > Bound.Right)
                return false;
            if (!double.IsInfinity(Bound.Top) && p.Y < Bound.Top)
                return false;
            if (!double.IsInfinity(Bound.Bottom) && p.Y > Bound.Bottom)
                return false;
            if (this._clip != null && !_clip.FillContains(p))
                return false;
            return true;
        }

        internal ControlInfo Parent
        {
            get { return _parent; }
            set { _parent = value; }
        }

        internal Collection<ControlInfo> Children
        {
            get
            {
                return _children;
            }
        }

        internal int ZIndex { get; set; }

        internal Brush Background { get; set; }

        internal GeneralTransform TransformToContent
        {
            get { return _transformToContent; }
            set 
            {
                _transformToContent = value;
                _transformFromContent = _transformToContent.Inverse;
            }
        }

        internal GeneralTransform TransformFromContent
        {
            get { return _transformFromContent; }
        }

        internal IObjectEntity Entity 
        {
            get { return _entity; }
            set { _entity = value; }
        }

        internal IControl Control
        {
            get { return _control; }
            set { _control = value; }
        }

        internal Rect Bound
        {
            get { return _bound; }
            set { _bound = value; }
        }

        internal void FreshClip()
        {
            if (Control.Clip == null)
            {
                this._clip = null;
            }
            else
            {
                this._clip = ((Geometry)Control.Clip).CloneCurrentValue();
                this._clip.Freeze();
            }
        }

        public void Fresh()
        {
            if (DesignerProperties.GetIsInDesignMode(Control as FrameworkElement))
            {
                return;
            }

            lock (this)
            {
                if (UTableHelper.CurrentVisualTree == null || !(UTableHelper.CurrentVisualTree is UObject))
                    return;
                this.Entity = (UTableHelper.CurrentVisualTree as UObject).Entity;
                this.Width = Control.Width;
                this.Height = Control.Height;
                try
                {
                    if (!(Control == null))
                    {
                        this.TransformToContent =
                        (Control as FrameworkElement).TransformToVisual(UTableHelper.CurrentVisualTree.TreeRoot as UObject);
                        this.TransformFromContent.Freeze();
                        this.TransformToContent.Freeze();
                        FreshClip();
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                    // Console.WriteLine("Failed! " + Control);
                }
            }
            // update all the children
            foreach (ControlInfo info in Children)
            {
                info.Fresh();
            }
        }

        internal void OnPropertyChanged(DependencyProperty property, object oldValue, object newValue)
        {
            if (property == FrameworkElement.HeightProperty || property == FrameworkElement.ActualHeightProperty)
            {
                HeightChanged(oldValue, newValue);
                return;
            }
            if (property == FrameworkElement.WidthProperty || property == FrameworkElement.ActualWidthProperty)
            {
                WidthChanged(oldValue, newValue);
            }
            if (property == FrameworkElement.MinWidthProperty)
            {
                MinWidthChanged(oldValue, newValue);
            }
            if (property == FrameworkElement.MinHeightProperty)
            {
                MinHeightChanged(oldValue, newValue);
            }
            if (property == FrameworkElement.MaxWidthProperty)
            {
                MaxWidthChanged(oldValue, newValue);
            }
            if (property == FrameworkElement.MaxHeightProperty)
            {
                MaxHeightChanged(oldValue, newValue);
            }
            if (property == FrameworkElement.OpacityProperty)
            {
                OpacityChanged(oldValue, newValue);
            }
            if (property == FrameworkElement.VisibilityProperty)
            {
                VisibilityChanged(oldValue, newValue);
            }
            if (property == FrameworkElement.IsHitTestVisibleProperty)
            {
                IsHitTestVisibleChanged(oldValue, newValue);
            }
            if (property == FrameworkElement.ClipProperty)
            {
                ClipChanged(oldValue, newValue);
            }
            if (property == Panel.ZIndexProperty)
            {
                ZIndexChanged(oldValue, newValue);
            }
            if (property == System.Windows.Controls.Control.BackgroundProperty)
            {
                BackgroundChanged(oldValue, newValue);
            }
            if (property == System.Windows.Controls.Panel.BackgroundProperty)
            {
                BackgroundChanged(oldValue, newValue);
            }
            if (property == ContentControl.ContentProperty)
            {
                OnContentChanged(oldValue, newValue);
            }
            if (property == FrameworkElement.RenderTransformProperty)
            {
                OnRenderTransformChanged(oldValue, newValue);
            }
        }

        #region IVisualNode Members

        public string ID
        {
            get { return _id; }
            set { _id = value; }
        }

        public double Width
        {
            get { return _width; }
            set { _width = value; }
        }

        public double Height
        {
            get { return _height; }
            set { _height = value; }
        }

        public Geometry Clip
        {
            get { return _clip; }
            set { _clip = value; }
        }

        public Visibility Visibility { get; set; }

        internal bool IsHitTestEnabled
        {
            get { return _isHitTestEnabled; }
            set { _isHitTestEnabled = value; }
        }

        public new bool IsHitTestVisible
        {
            get { return _isHitTestVisible; }
            set { _isHitTestVisible = value; }
        }

        public double Opacity { get; set; }

        public Point TranslatePoint2(Point point, IVisualNode relativeTo)
        {
            return (relativeTo as ControlInfo).TransformFromContent.Transform(this.TransformToContent.Transform(point));
        }

        public Point TranslatePoint(Point point, IVisualNode relativeTo)
        {
            if ((relativeTo as ControlInfo).Control is UTextBlock)
            {
                Console.WriteLine("Canvas Position: " + point);
            }

            if (! (relativeTo is ControlInfo))
            {
                return new Point(double.NaN, double.NaN);
            }
            return (relativeTo as ControlInfo).TransformFromContent.Transform(this.TransformToContent.Transform(point));
        }

        public HitTestResult HitTest(HitTestParameters hitTestParameters)
        {
            lock(this)
            {
                if (hitTestParameters is PointHitTestParameters)
                {
                    Point p = (hitTestParameters as PointHitTestParameters).HitPoint;

                    // hit test prerequisite
                    if ((this.Visibility != Visibility.Visible) ||
                        (this.Opacity == 0) ||
                        !IsInBound(p))
                    {
                        return new PointHitTestResult(null);
                    }

                    // firstly see if the hit the child
                    // ensure that the sort is stable
                    // _sortedChildren.Reverse();
                    // _sortedChildren.Sort(_comparer);
                    foreach (ControlInfo child in _sortedChildren)
                    {
                        HitTestResult result = child.HitTest(new PointHitTestParameters(this.TranslatePoint(p, child)));
                        if (result != null && result.VisualHit != null)
                        {
                            return result;
                        }
                    }

                    IVisualNode visualHit = null;
                    if ((this.Opacity != 0) &&
                        (this.IsHitTestEnabled == true) &&
                        (this.IsHitTestVisible == true) &&
                        (p.X >= 0 && p.X <= this.Width && p.Y >= 0 && p.Y <= this.Height) &&
                        (this.Clip == null || this.Clip.FillContains(p)))
                    {
                        visualHit = this;
                    }
                    return new PointHitTestResult(visualHit);
                }
            }
            return null;
        }

        #endregion

        #region IInputNode Members

        public void RaiseEvent(InputEventArgs e)
        {
            throw new NotImplementedException();
        }

        public void OnInputReceived(InputEventArgs args)
        {
            throw new NotImplementedException();
        }

        public bool CaptureFinger(int fingerId)
        {
            (_control as UIElement).Dispatcher.BeginInvoke(new CaptureFingerHandler(_control.CaptureFinger), new object[] { fingerId });
            return true;
        }

        public delegate bool CaptureFingerHandler(int fingerid);

        public bool UncaptureFinger(int fingerId)
        {
            (_control as UIElement).Dispatcher.BeginInvoke(new CaptureFingerHandler(_control.UncaptureFinger), new object[] { fingerId });
            return true;
        }

        public event FingerEventHandler GotFingerCapture;

        public event FingerEventHandler LostFingerCapture;

        public event InputEventHandler InputReceived;

        #endregion
    }
}
